#include <net/arp.h>
using namespace myos;
using namespace myos::common;
using namespace myos::net;
using namespace myos::drivers;


// void printf(char*);
// void printfHex(uint8_t);

AddressResolutionProtocol::AddressResolutionProtocol(EtherFrameProvider* backend)
: EtherFrameHandler(backend, 0x806)
{
    numCacheEntries = 0;
}

AddressResolutionProtocol::~AddressResolutionProtocol()
{

}


bool AddressResolutionProtocol::OnEtherFrameReceived(uint8_t* etherframePayload, uint32_t size)
{
    if(size < sizeof(AddressResolutionProtocolMessage))
        return false;
    
    AddressResolutionProtocolMessage* arp = (AddressResolutionProtocolMessage*)etherframePayload;
    if(arp->hardwareType == 0x0100)
    {
        if(arp->protocol == 0x0008
        && arp->hardwareAddressSize == 6
        && arp->protocolAddressSize == 4
        && arp->dstIP == backend->GetIPAddress())
        {
            switch (arp->command)
            {   
            case 0x0100:        //request
                arp->command = 0x0200;
                arp->dstIP = arp->srcIP;
                arp->dstMAC = arp->srcMAC;
                arp->srcIP = backend->GetIPAddress();
                arp->srcMAC = backend->GetMACAddress();
                return true;
                break;

            case 0x0200:        //response
                if(numCacheEntries < 128)
                {
                    // printf("get arp response: \n");
                    IPcache[numCacheEntries] = arp->srcIP;
                    MACcache[numCacheEntries] = arp->srcMAC;
                    // printf("get IP: \n");
                    // printfHex(arp->srcIP & 0xFF);
                    // printfHex(arp->srcIP >> 8& 0xFF);
                    // printfHex(arp->srcIP >> 16& 0xFF);
                    // printfHex(arp->srcIP >> 24& 0xFF);   
                    // printf("get MAC: \n");
                    // printfHex(arp->srcMAC & 0xFF);
                    // printfHex(arp->srcMAC >> 8& 0xFF);
                    // printfHex(arp->srcMAC >> 16& 0xFF);
                    // printfHex(arp->srcMAC >> 24& 0xFF);
                    // printfHex(arp->srcMAC >> 32& 0xFF);
                    // printfHex(arp->srcMAC >> 40& 0xFF);
                    
                    numCacheEntries++;

                }     
                break;
            }
        }

    }

    return false;
}

void AddressResolutionProtocol::BroadcastMACAddress(uint32_t IP_BE)
{
    AddressResolutionProtocolMessage arp;
    arp.hardwareType = 0x0100;  //ethernet
    arp.protocol = 0x0008; // ipv4
    arp.hardwareAddressSize = 6;  //mac
    arp.protocolAddressSize = 4;  //ipv4
    arp.command = 0x0200;         //response

    arp.srcMAC = backend->GetMACAddress();
    arp.srcIP = backend->GetIPAddress();
    arp.dstMAC = Resolve(IP_BE);
    arp.dstIP = IP_BE;

    this->Send(arp.dstMAC, (uint8_t*)&arp, sizeof(AddressResolutionProtocolMessage));
}

void AddressResolutionProtocol::RequestMACAddress(uint32_t IP_BE)
{
    AddressResolutionProtocolMessage arp;
    arp.hardwareType = 0x0100; //ethernet
    arp.protocol = 0x0008; //ipv4
    arp.hardwareAddressSize = 6; //MAC
    arp.protocolAddressSize = 4; //ipV4
    arp.command = 0x0100;   //request
    arp.srcMAC = backend->GetMACAddress();
    arp.srcIP = backend->GetIPAddress();
    arp.dstMAC = 0xFFFFFFFFFFFF; //boardcast
    arp.dstIP = IP_BE;

    this->Send(arp.dstMAC, (uint8_t*)&arp, sizeof(AddressResolutionProtocolMessage)); 
}

uint64_t AddressResolutionProtocol::GetMACFromCache(uint32_t IP_BE)
{
    for(int i = 0; i < numCacheEntries; i++)
        if(IPcache[i] == IP_BE)
            return MACcache[i];
    return 0xFFFFFFFFFFFF;  //broadcast address
}

// void printf(char*);
// void printfHex(uint8_t);

uint64_t AddressResolutionProtocol::Resolve(uint32_t IP_BE)
{
    uint64_t result = GetMACFromCache(IP_BE);
    if(result == 0xFFFFFFFFFFFF)
        RequestMACAddress(IP_BE);
        //printf("send arp request\0");

    while(result == 0xFFFFFFFFFFFF)  //prossible infinte loop
        result = GetMACFromCache(IP_BE);
    
    
    return result;

}